package cn.demoncat.util.exec;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.io.CloseUtil;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.lang.JsonUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.lang.constant.StatusConstant.Common;
import cn.demoncat.util.lang.constant.SystemConstant;
import cn.demoncat.util.lang.entity.LineReadCallback;
import cn.demoncat.util.lang.entity.LineReadCallback.LineReadFinishException;
import cn.demoncat.util.lang.entity.TypeList;
import org.apache.commons.collections4.CollectionUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

/**
 * 执行系统命令 - java原生进程工具
 * 
 * @author 延晓磊
 *
 * @since 2019年6月13日
 */
public class ProcessUtil {

	/**
	 * 输出结果编码（测试时临时修改）
	 */
	public static String CHARSET = CharsetConstant.UTF8;
	
	/**
	 * 执行系统命令
	 * 
	 * @param command	命令
	 * 
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> exec(String... command) {
		// 执行系统命令
		Process process = getProcess(command);
		try {
			// 读取成功响应
			List<String> success = IoUtil.readToStrs(process.getInputStream(),CHARSET);
	        // 读取错误响应
			List<String> error = IoUtil.readToStrs(process.getErrorStream(),CHARSET);
			// 返回结果
			if (CollectionUtils.isNotEmpty(success)) {
				return new TypeList<>(Common.YES, success);
			} else {
				return new TypeList<>(Common.NO, error);
			}
		} catch (Exception e) {
			throw new IoRuntimeException("读取系统命令失败：command = " + JsonUtil.toStr(command), e);
		} finally {
			// 停止进程
			process.destroy();
		}
	}
	
	/**
	 * 执行系统命令
	 * 
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	命令
	 * @return 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void exec(LineReadCallback callback, Long timeout, String... command) {
		// 执行系统命令
		Process process = getProcess(command);
		// 无限超时
		if (timeout == null || timeout < 1) {
			// 直接读取
			readProcess(process, callback, command);
		}else {
			// 启动子线程读取
			Thread thread = new Thread(new ProcessThread(process,callback,command));
			thread.start();
			// 同步等待子线程
			try {
				thread.join(timeout);
			} catch (InterruptedException e) {
				// 本线程被中断
				thread.interrupt();
			}
			// 停止进程：停止读取，完成并退出子线程
			if (process != null) {
				process.destroy();
			}
		}
	}

	/**
	 * 执行shell或cmd命令
	 *
	 * @param command	shell文件/多个命令
	 *
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> osExec(String... command) {
		if (SystemConstant.OS_IS_LINUX) {
			return shell(command);
		}else{
			return cmd(command);
		}
	}

	/**
	 * 执行shell或cmd命令
	 *
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	shell文件/多个命令
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void osExec(LineReadCallback callback, Long timeout, String... command) {
		if (SystemConstant.OS_IS_LINUX) {
			shell(callback, timeout, command);
		}else{
			cmd(callback, timeout, command);
		}
	}

	/**
	 * 执行shell脚本、命令
	 * 
	 * @param command	shell文件/多个命令
	 * 
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> shell(String... command) {
		return exec(ExecUtil.BASH, ExecUtil.BASHC, ExecUtil.moreCommand(command));
	}
	
	/**
	 * 执行shell脚本、命令
	 * 
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	shell文件/多个命令
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void shell(LineReadCallback callback, Long timeout, String... command) {
		exec(callback,timeout, ExecUtil.BASH, ExecUtil.BASHC, ExecUtil.moreCommand(command));
	}
	
	/**
	 * 执行cmd命令
	 * 
	 * @param command	多个命令
	 * 
	 * @return 输出内容{1:[输出流内容], 0:[错误流内容]}。注意，输出类型不能作为执行状态判断标准
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static TypeList<String> cmd(String... command) {
		return exec(ExecUtil.CMD,ExecUtil.CMDC, ExecUtil.moreCommand(command));
	}
	
	/**
	 * 执行cmd命令
	 * 
	 * @param callback	响应读取器
	 * @param timeout	读取超时：毫秒，用于自动中断读取，默认0/null（无限）
	 * @param command	多个命令
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	public static void cmd(LineReadCallback callback, Long timeout, String... command) {
		exec(callback,timeout, ExecUtil.CMD,ExecUtil.CMDC, ExecUtil.moreCommand(command));
	}
	
	
	/**
	 * 线程Process
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月14日
	 */
	public static class ProcessThread implements Runnable{
		// 进程
		private final Process process;
		// 响应读取器
		private final LineReadCallback callback;
		// 命令
		private final String[] command;
		
		public ProcessThread(Process process, LineReadCallback callback, String[] command) {
			super();
			this.process = process;
			this.callback = callback;
			this.command = command;
		}

		@Override
		public void run() {
			// 读取命令响应
			readProcess(process, callback, command);
		}
	}
	
	/**
	 * 执行系统命令，获取执行进程
	 * 
	 * @param command	命令
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	private static Process getProcess(String[] command) {
		Process process;
		try {
			// 通过Runtime执行命令
			if (command.length == 1) {
				// 通过Runtime创建子进程来执行command，子进程通过inputStream,errorStream,outStream与主进程交互
				process = Runtime.getRuntime().exec(command[0]);
			}else {
				process = Runtime.getRuntime().exec(command);
			}
			return process;
		} catch (IOException e) {
			throw new IoRuntimeException("执行系统命令失败：command = " + JsonUtil.toStr(command), e);
		}
	}
	
	/**
	 * 读取命令执行响应
	 * 
	 * @param process	进程
	 * @param callback	响应读取器
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年6月13日
	 */
	private static void readProcess(Process process, LineReadCallback callback, String[] command) {
		BufferedReader reader = null;
		BufferedReader errorReader = null;
		try {
			// 读取成功响应
			reader = new BufferedReader(new InputStreamReader(process.getInputStream(), CHARSET));
			String line = reader.readLine();
	        while (line != null) {
	        	if (callback.getStatus()) {
	        		callback.read(line);
	 	            line = reader.readLine();
				}else {
					break;
				}
	        }
	        // 读取错误响应
	      	errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), CHARSET));
	        String errorLine = errorReader.readLine();
	        while (errorLine != null) {
	        	if (callback.getStatus()) {
	        		callback.readError(errorLine);
		            errorLine = errorReader.readLine();
				}else {
					break;
				}
	        }
		} catch (LineReadFinishException e) {
			// 停止读取
		} catch (IOException e) {
			throw new IoRuntimeException("读取命令执行结果失败：command = " + JsonUtil.toStr(command), e);
		} finally {
			CloseUtil.close(errorReader);
			CloseUtil.close(reader);
			// 停止进程
			process.destroy();
		}
	}
	
}
